/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
** Contact: Qt Software Information (qt-info@nokia.com)
**
** This file is part of the Qt Messaging Framework.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the either Technology Preview License Agreement or the
** Beta Release License Agreement.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain
** additional rights. These rights are described in the Nokia Qt LGPL
** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
** package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at qt-sales@nokia.com.
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qcopchannelmonitor.h"
#include "qcopchannel_p.h"
#include <QtCore/qtimer.h>

/* ! - documentation comments in this file are disabled:
    \class QCopChannelMonitor
    \brief The QCopChannelMonitor class allows applications to monitor when other applications register and unregister QCopChannels.

    The QCop messaging system is a simple interprocess communication
    mechanism that delivers messages and associated data payloads to
    named "channels".  The system is reliable end-to-end if another
    application is listening on the designated channel.  But if no
    application is listening, the message is silently discarded.

    Normally it isn't a problem if a QCop message is discarded, because
    the lack of the designated channel means that the service is simply
    unavailable and nothing should happen.  But sometimes an application
    knows that the destination channel should be there and wants to
    be notified when it is no longer present; usually because the
    destination application has exited or crashed.

    The QCopChannelMonitor class allows client applications to monitor
    the registration state of channel names in the system in a reliable
    manner.  Multiple applications may be listening to the same channel,
    but as soon as they all stop listening, the application using
    QCopChannelMonitor will be notified via the unregistered() signal.

    If a new application starts listening on a channel that had no
    prior listeners, then the application using QCopChannelMonitor will
    be notified via the registered() signal.

    The QCopChannel::isRegistered() function provides similar functionality,
    but it blocks execution and needs to be polled to provide up to date
    information.  The QCopChannelMonitor class operates asynchonrously
    and does not require the registration state to be polled.

    As an example, we will define a simple media playing server that
    listens on the well-known QCop channel \c{QPE/MediaServer}.  Clients
    send a start message to the server that contains the URL of the media
    document to be played, and the name of a client-specific QCop
    channel for status messages from the server.

    The client starts by creating its status message channel:

    \code
    QCopChannel *channel = new QCopChannel("QPE/MediaClient/23");
    connect(channel, SIGNAL(received(QString,QByteArray)),
            this, SLOT(statusMessage(QString,QByteArray)));
    \endcode

    The \c{23} in the above example will normally be replaced with a
    globally-unique identifier generated by the client.  The client then
    sends a QCop message to the media server's well-known channel:

    \code
    QCopEnvelope env("QPE/MediaServer", "start(QString,QString)");
    env << "http://www.foobar.com/media.mpg";
    env << "QPE/MediaClient/23";
    \endcode

    The server unpacks the start message and creates a QCopChannelMonitor
    for the client's status channel:

    \code
    QCopChannelMonitor *monitor = new QCopChannelMonitor("QPE/MediaClient/23");
    connect(monitor, SIGNAL(unregistered()), this, SLOT(clientUnregistered()));
    \endcode

    The server can then send status messages to the client as the media
    document is played.  If the \c{clientUnregistered()} slot is called,
    then the server knows that the client is no longer running and can
    halt media playback and the sending of status messages.

    \sa QCopChannel
*/

/* !
    Constructs a new QCop channel monitor for \a channel and
    attaches it to \a parent.

    If \a channel is already registered by one of the clients in the
    system, then the registered() signal will be emitted after this
    constructor exits and control returns to the event loop.

    If the channel is not already registered, then the unregistered()
    signal will be emitted instead.

    The state() will be Unknown until the initial registration
    status has been determined.

    \sa registered(), unregistered(), state()
*/
QCopChannelMonitor::QCopChannelMonitor(const QString& channel, QObject *parent)
    : QObject(parent)
{
    d = new QCopChannelMonitorPrivate(this, channel);
    d->ref.ref();

    QCopThreadData *td = QCopThreadData::instance();

    // Do we need a new monitor list for this channel name?
    QCopClientMonitorMap::Iterator it = td->clientMonitorMap.find(channel);
    if (it != td->clientMonitorMap.end()) {
        it.value().append(QCopChannelMonitorPrivatePointer(d));

        // Copy the state from the previous object on this channel.
        // Either the state is already known, or a request is
        // in transit from the previous object and we will get the
        // update at the same time.
        const QCopChannelMonitorPrivate *prev = it.value()[0].constData();
        d->state = prev->state;
        if (d->state == QCopChannelMonitor::Registered)
            QTimer::singleShot(0, this, SIGNAL(registered()));
        else if (d->state == QCopChannelMonitor::Unregistered)
            QTimer::singleShot(0, this, SIGNAL(unregistered()));
        return;
    }

    it = td->clientMonitorMap.insert
        (channel, QList<QCopChannelMonitorPrivatePointer>());
    it.value().append(QCopChannelMonitorPrivatePointer(d));

    // Inform the server about this channel
    td->clientConnection()->sendChannelCommand(QCopCmd_RegisterMonitor, channel);
}

/* !
    Destroys this QCop channel monitor.
*/
QCopChannelMonitor::~QCopChannelMonitor()
{
    QCopThreadData *td = QCopThreadData::instance();

    QCopClientMonitorMap::Iterator it = td->clientMonitorMap.find(d->channel);
    Q_ASSERT(it != td->clientMonitorMap.end());
    it.value().removeAll(QCopChannelMonitorPrivatePointer(d));
    // Still any monitors connected locally?
    if (it.value().isEmpty()) {
        if (td->hasClientConnection()) {
            td->clientConnection()->sendChannelCommand
                (QCopCmd_DetachMonitor, d->channel);
        }
        td->clientMonitorMap.remove(d->channel);
    }

    // Dereference the private data structure.  It may stay around
    // for a little while longer if it is in use by handleRegistered()
    // or handleUnregistered().
    d->object = 0;
    if (!d->ref.deref())
        delete d;
}

/* !
    Returns that QCop channel that this object is monitoring.

    \sa state()
*/
QString QCopChannelMonitor::channel() const
{
    return d->channel;
}

/* !
    \enum QCopChannelMonitor::State
    This enum defines the state of a QCop channel that is being monitored by QCopChannelMonitor.

    \value Unknown It is unknown whether the QCop channel is registered or
        unregistered; the system is in the process of determining the state.
    \value Registered The QCop channel is known to have been registered.
    \value Unregistered The QCop channel is known to have been unregistered.
*/

/* !
    Returns the current state of the QCop channel that is being
    monitored by this object.

    \sa registered(), unregistered()
*/
QCopChannelMonitor::State QCopChannelMonitor::state() const
{
    return QCopChannelMonitor::State(d->state);
}

/* !
    \fn void QCopChannelMonitor::registered()

    Signal that is emitted when the channel is registered by one of
    the clients in the system.  If another client registers for the
    channel, the registered() signal will not be repeated.  Once
    all clients have unregistered the channel, the unregistered()
    signal will be emitted.

    \sa unregistered(), state()
*/

/* !
    \fn void QCopChannelMonitor::unregistered()

    Signal that is emitted once the channel has been unregistered by
    all of the clients in the system.

    \sa registered(), state()
*/

void QCopClient::handleRegisterMonitor(const QString& ch)
{
    QCopThreadData *td = QCopThreadData::instance();

    // Add the monitor to the server's list.
    QCopServerMap::Iterator it = td->serverMonitorMap.find(ch);
    if (it == td->serverMonitorMap.end())
        it = td->serverMonitorMap.insert(ch, QList<QCopClient*>());
    it.value().append(this);

    // Inform the client about the current state of the channel.
    if (td->serverMap.contains(ch))
        sendChannelCommand(QCopCmd_MonitorRegistered, ch);
    else
        sendChannelCommand(QCopCmd_MonitorUnregistered, ch);
}

void QCopClient::handleDetachMonitor(const QString& ch)
{
    QCopThreadData *td = QCopThreadData::instance();
    QCopServerMap::Iterator it = td->serverMonitorMap.find(ch);
    if (it != td->serverMonitorMap.end()) {
        it.value().removeAll(this);
        if (it.value().isEmpty())
            td->serverMap.erase(it);
    }
}

void QCopClient::handleRegistered(const QString& ch)
{
    QCopThreadData *td = QCopThreadData::instance();
    QList<QCopChannelMonitorPrivatePointer> clients = td->clientMonitorMap[ch];
    for (int i = 0; i < clients.size(); ++i) {
	QCopChannelMonitorPrivate *monitor = clients.at(i).data();
	if (monitor->object) {
            monitor->state = QCopChannelMonitor::Registered;
	    emit monitor->object->registered();
        }
    }
}

void QCopClient::handleUnregistered(const QString& ch)
{
    QCopThreadData *td = QCopThreadData::instance();
    QList<QCopChannelMonitorPrivatePointer> clients = td->clientMonitorMap[ch];
    for (int i = 0; i < clients.size(); ++i) {
	QCopChannelMonitorPrivate *monitor = clients.at(i).data();
	if (monitor->object) {
            monitor->state = QCopChannelMonitor::Unregistered;
	    emit monitor->object->unregistered();
        }
    }
}
